package org.simplextensions.registry;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.simplextensions.configuration.BundleConfiguration;
import org.simplextensions.configuration.ExtensionsConfiguration.CreationPhaseListener;
import org.simplextensions.configuration.ExtensionsConfiguration.RegisterPhaseListener;
import org.simplextensions.configuration.ExtensionsConfiguration.ScanPhaseListeners;
import org.simplextensions.configuration.ExtensionsConfiguration.ValidatePhaseListener;
import org.simplextensions.configuration.PhaseListener;
import org.simplextensions.configuration.ScanPhaseListener;
import org.simplextensions.configuration.ScanPhaseListenerType;
import org.simplextensions.registry.phaselisteners.ICreationPhaseListener;
import org.simplextensions.registry.phaselisteners.IRegisterPhaseListener;
import org.simplextensions.registry.phaselisteners.IValidationPhaseListener;
import org.simplextensions.scanner.IScanPhaseListener;

public class Bundle {

	private final IExtensionRegistry extensionRegistry;

	private List<IRegisterPhaseListener> registerListeners = new LinkedList<IRegisterPhaseListener>();

	private List<ICreationPhaseListener> creationListeners = new LinkedList<ICreationPhaseListener>();

	private Queue<IValidationPhaseListener> validationListeners = new ConcurrentLinkedQueue<IValidationPhaseListener>();

	private Map<String, Map<Integer, List<IScanPhaseListener>>> scanListeners = new HashMap<String, Map<Integer, List<IScanPhaseListener>>>();

	private String path;

	private final String name;

	private final Set<String> classNames;

	public Bundle(IExtensionRegistry extensionRegistry, BundleConfiguration configuration) throws Exception {
		this(extensionRegistry, configuration.getName(), configuration.getClassNames());

		for (CreationPhaseListener cpl : configuration.getCreationPhaseListener()) {
			for (PhaseListener listener : cpl.getListener())
				creationListeners.add((ICreationPhaseListener) Class.forName(listener.getClassName()).newInstance());
		}

		for (ValidatePhaseListener cpl : configuration.getValidatePhaseListener()) {
			for (PhaseListener listener : cpl.getListener())
				validationListeners.add((IValidationPhaseListener) Class.forName(listener.getClassName()).newInstance());
		}

		for (RegisterPhaseListener cpl : configuration.getRegisterPhaseListener()) {
			for (PhaseListener listener : cpl.getListener())
				registerListeners.add((IRegisterPhaseListener) Class.forName(listener.getClassName()).newInstance());
		}

		for (ScanPhaseListeners spl : configuration.getScanPhaseListeners()) {
			for (ScanPhaseListener l : spl.getScanListener()) {
				IExtensionScanPhaseListener scanPhaseListener = (IExtensionScanPhaseListener) Class.forName(l.getClassName()).newInstance();
				int level = l.getType() == ScanPhaseListenerType.PRIMARY ? 1 : 2;
				addScanPhaseListener(extensionRegistry, l.getAnnotation(), scanPhaseListener, level);
			}
		}

		URL url = configuration.getUrl();
		String protocol = url.getProtocol();

		String path2 = url.getPath();
		if ("jar".equals(protocol)) {
			path = path2.substring(6, path2.indexOf("!"));
		} else if ("file".equals(protocol)) {
			path = new File(path2).getParent();
		}

	}

	public Bundle(IExtensionRegistry extensionRegistry, String name) {
		this(extensionRegistry, name, null);
	}

	public Bundle(IExtensionRegistry extensionRegistry, String name, List<String> classNames) {
		this.extensionRegistry = extensionRegistry;
		this.name = name;
		this.classNames = classNames != null ? new HashSet<String>(classNames) : null;

		creationListeners.addAll(extensionRegistry.getCreationListeners());
		validationListeners.addAll(extensionRegistry.getValidationListeners());
		registerListeners.addAll(extensionRegistry.getRegisterListeners());

		Map<Class<?>, Map<Integer, Collection<IExtensionScanPhaseListener>>> sl = extensionRegistry.getScanListeners();
		for (Entry<Class<?>, Map<Integer, Collection<IExtensionScanPhaseListener>>> e : sl.entrySet()) {
			Class<?> annotationClass = e.getKey();
			Map<Integer, Collection<IExtensionScanPhaseListener>> value = e.getValue();
			for (Entry<Integer, Collection<IExtensionScanPhaseListener>> e2 : value.entrySet()) {
				Integer level = e2.getKey();
				Collection<IExtensionScanPhaseListener> listeners = e2.getValue();
				for (IExtensionScanPhaseListener scanPhaseListener : listeners) {
					addScanPhaseListener(extensionRegistry, annotationClass.getCanonicalName(), scanPhaseListener, level);
				}
			}
		}
	}

	public String getName() {
		return name;
	}

	public Set<String> getClassNames() {
		return classNames != null ? Collections.unmodifiableSet(classNames) : null;
	}

	private void addScanPhaseListener(IExtensionRegistry extensionRegistry, String annotationClassName,
			IExtensionScanPhaseListener scanPhaseListener, int level) {
		Map<Integer, List<IScanPhaseListener>> map = this.scanListeners.get(annotationClassName);
		if (map == null) {
			this.scanListeners.put(annotationClassName, map = new HashMap<Integer, List<IScanPhaseListener>>());
		}
		List<IScanPhaseListener> list = map.get(level);
		if (list == null) {
			map.put(level, list = new LinkedList<IScanPhaseListener>());
		}
		list.add(new ScanPhaseListenerAdapter(extensionRegistry, this, scanPhaseListener));
	}

	public IExtensionRegistry getExtensionRegistry() {
		return extensionRegistry;
	}

	public List<IRegisterPhaseListener> getRegisterListeners() {
		return Collections.unmodifiableList(this.registerListeners);
	}

	public List<ICreationPhaseListener> getCreationListeners() {
		return Collections.unmodifiableList(this.creationListeners);
	}

	public List<IValidationPhaseListener> getValidationListeners() {
		return Collections.unmodifiableList(new ArrayList<IValidationPhaseListener>(this.validationListeners));
	}

	public void addValidationPhaseListener(IValidationPhaseListener listener) {
		this.validationListeners.add(listener);
	}

	public List<IScanPhaseListener> getScanListeners(String anntationClassName) {
		Map<Integer, List<IScanPhaseListener>> map = this.scanListeners.get(anntationClassName);
		if (map == null)
			return null;

		List<IScanPhaseListener> list = new LinkedList<IScanPhaseListener>();

		Integer[] array = map.keySet().toArray(new Integer[map.keySet().size()]);
		Arrays.sort(array);
		for (Integer i : array) {
			list.addAll(map.get(i));
		}
		return Collections.unmodifiableList(list);
	}

	public String getClassesPath() {
		return this.path;
	}

}
